//-------------------------------------------------------------------
//	Copyright (c) 2012-2014 Zhirnov Andrey
//	This file is part of the "UXGEN-ES" project.
//	See copyright notice in "Engine.h".
//-------------------------------------------------------------------

#pragma once

#include "Utils/Base/Core.h"
#include "Utils/Shader/ShaderConfig/ShaderProgram.h"
#include "Shared/Resources/ResourceFileInfo.h"

namespace Engine
{
namespace Utils
{



	//
	// Shader Config Loader
	//

	class ShaderConfigLoader
	{
	// types
	private:
		typedef map< string, string >		shader_source_t;


	// variables
	private:
		Ptr< UtilsCore >	_core;
		shader_source_t		_shaderSources;
		ShaderUtils			_utils;


	// methods
	public:
		ShaderConfigLoader (UtilsCore *core);
		~ShaderConfigLoader ();

		bool LoadShader (uni_c_string filename, string &shaderSource);
		bool LoadProgram (uni_c_string filename, uni_c_string shadersDir, ShaderProgramCfg &program);
		bool LoadConfig (uni_c_string filename, uni_c_string outputPath, bool clearOutputDir);
		
		bool SaveProgram (uni_c_string filename, const MetaData::ProgramCfg &prog, const string &password) const;

		void Clear();


	// metadata
	public:
		struct ProgList
		{
			string_array_t	programs;
			string			outputDir;
			string			inputDir;
			string			shadersDir;
			string			extension;
			string			password;
		};

		static CMetaClass &	MetaProgList ()		{ static CMetaClass metaClass;	return metaClass; }


		static void InitMetaData ();
	};


	
	inline UX_CREATE_META_OBJECT( ShaderConfigLoader::ProgList,	ShaderConfigLoader::MetaProgList() );


	// constructor
	inline ShaderConfigLoader::ShaderConfigLoader (UtilsCore *core) : _core(core)
	{
		InitMetaData();
	}


	// destructor
	inline ShaderConfigLoader::~ShaderConfigLoader ()
	{}


	// LoadShader
	inline bool ShaderConfigLoader::LoadShader (uni_c_string filename, string &shaderSource)
	{
		RFilePtr	file;
		CHECK_ERR( (file = File::HddRFile::New( filename )).IsNotNull() );

		const usize	file_size = file->Size();

		shaderSource.Reserve( file_size + 1 );

		CHECK_ERR( file->Read( shaderSource.ptr(), file_size ) );

		shaderSource.SetLength( file_size );

		_shaderSources.Add( filename, shaderSource );
		return true;
	}


	// LoadProgram
	inline bool ShaderConfigLoader::LoadProgram (uni_c_string filename, uni_c_string shadersDir, ShaderProgramCfg &program)
	{
		static_obj_tree_t			cfg_tree;

		cfg_tree.Push( TConfigObjInfo( program.GetMetaIterator(), null ) );

		CHECK_ERR( _core->Parse( filename, cfg_tree ) );

		string	fs_source;
		string	vs_source;
		string	fs_fname;
		string	vs_fname;

		FileAddressUtils::BuildPath( fs_fname, shadersDir, program.fragmentShader );
		FileAddressUtils::BuildPath( vs_fname, shadersDir, program.vertexShader );

		CHECK_ERR( LoadShader( fs_fname, fs_source ) );
		CHECK_ERR( LoadShader( vs_fname, vs_source ) );

		CHECK_ERR( program.OnLoad( fs_source, vs_source, &_utils ) );

		return true;
	}


	// LoadConfig
	inline bool ShaderConfigLoader::LoadConfig (uni_c_string filename, uni_c_string outputPath, bool clearOutputDir)
	{
		Clear();

		static_obj_tree_t	cfg_tree;

		ProgList			prog_list;

		cfg_tree.Push( TConfigObjInfo( MetaProgList().GetSetIter( prog_list ), null ) );

		const string	input_path = FileAddressUtils::GetPath( filename );

		_core->Config()->ClearDerectories();
		_core->Config()->AddDirectory( input_path );

		CHECK_ERR( _core->Parse( filename, cfg_tree ) );

		string	input_dir;
		string	output_dir;
		string	shaders_dir;

		FileAddressUtils::BuildPath( input_dir, input_path, prog_list.inputDir );
		FileAddressUtils::BuildPath( output_dir, outputPath, prog_list.outputDir );
		FileAddressUtils::BuildPath( shaders_dir, input_path, prog_list.shadersDir );
		
		// create output dir
		if ( not FileSystem::IsDirectoryExist( output_dir.cstr() ) )
		{
			FileSystem::NewDirectory( output_dir.cstr() );
		}

		if ( clearOutputDir )
		{
			_core->ClearDir( output_dir );
		}


		FOR( i, prog_list.programs )
		{
			ShaderProgramCfg	prog;

			string	in_fname;
			FileAddressUtils::BuildPath( in_fname, input_dir, prog_list.programs[i] );

			LoadProgram( in_fname, shaders_dir, prog );
			
			string	fname;
			FileAddressUtils::BuildPath( fname, output_dir, FileAddressUtils::GetName( prog_list.programs[i] ), prog_list.extension );

			SaveProgram( fname, prog, prog_list.password );
		}

		return true;
	}
	

	// SaveProgram
	inline bool ShaderConfigLoader::SaveProgram (uni_c_string filename, const MetaData::ProgramCfg &prog, const string &password) const
	{
		WFilePtr	file;
		CHECK_ERR( (file = File::HddWFile::New( filename )).IsNotNull() );

		RESFILE_WRITE_HEADER( file, ProgramFileInfo );

		if ( not password.Empty() )
		{
			const uint	unique_key = _shared_resources_::CryptKey::GenerateUniqueKey();
			file->Write( unique_key );

			// generate crypt key
			uint8_array_t	pw( uni_array<uint8>::From( password.ptr(), Min( password.Length(),
								File::SimpleWCryptFile::CryptAlgorithm::password_t::STATIC_COUNT ) ) );

			_shared_resources_::CryptKey::BuildKey( pw, unique_key );

			File::SimpleWCryptFile::CryptAlgorithm	alg( pw );


			WFilePtr	cfile;
			CHECK_ERR( (cfile = File::SimpleWCryptFile::New( file, alg )).IsNotNull() );
			file = cfile;
		}
		else
		{
			file->Write( uint(0) );		// without crypting
		}
		
		prog.Serialize( file );
		return true;
	}


	// Clear
	inline void ShaderConfigLoader::Clear()
	{
		_shaderSources.Clear();
	}
	

	// InitMetaData
	inline void ShaderConfigLoader::InitMetaData ()
	{
		CMetaAddIteratorPtr	iter;

		CMetaClass::s_InitBaseTypes();

		ShaderProgramCfg().InitMetaData();

		if ( not MetaProgList().IsInit() )
		{
			ProgList	obj;

			MetaProgList().CreateExt( "ProgramList", obj );
			iter = MetaProgList().GetAddIter( obj );

			iter->AddField( obj.programs,	"programs" );
			iter->AddField( obj.outputDir,	"output_dir" );
			iter->AddField( obj.extension,	"extension" );
			iter->AddField( obj.inputDir,	"input_dir" );
			iter->AddField( obj.shadersDir,	"shaders_dir" );
			iter->AddField( obj.password,	"password" );
		}
	}

}	// Utils
}	// Engine